package com.googlecode.javarpctp.server;

import com.googlecode.javarpctp.test.common.ThreadCreatedEvent;
import com.googlecode.javarpctp.client.ClientMessage;
import com.googlecode.javarpctp.communication.ConnectionHandler;
import com.googlecode.javarpctp.communication.ConnectionState;
import com.googlecode.javarpctp.communication.ConnectionStateChangedListener;
import com.googlecode.javarpctp.communication.ConnectionThread;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.logging.Level;
import javax.swing.event.EventListenerList;
import com.googlecode.javarpctp.test.common.Logger;
import com.googlecode.javarpctp.test.common.ThreadListener;
import com.googlecode.javarpctp.communication.ConnectionHandlerMessageTransportEvent;
import com.googlecode.javarpctp.communication.ConnectionHandlerMessageTransportListener;
import com.googlecode.javarpctp.communication.ConnectionStateChangedEvent;
import com.googlecode.javarpctp.communication.CustomMessageRecievedEvent;
import com.googlecode.javarpctp.communication.CustomMessageRecievedListener;
import com.googlecode.javarpctp.communication.Message;
import com.googlecode.javarpctp.communication.StopCause;
import com.googlecode.javarpctp.remote.InvocationResponsiveMessage;
import com.googlecode.javarpctp.remote.RemoteDispatcher;

public class Server implements Runnable {

    final private ArrayList<ConnectionThread> connections = new ArrayList<ConnectionThread>();
    protected EventListenerList listenerList = new EventListenerList();
    private RemoteDispatcher remoteDispatcher = new RemoteDispatcher();
    private int port;
    private ServerSocket serverSocket;
    private ServerState state;
    private int connectionID;
    private StopCause stopCause;
    private Exception stopException;

    ;

    public Server(int port) {
        this.port = port;
        this.setState(ServerState.CREATED);
        this.init();
    }

    private void init() {
        this.connections.clear();
        this.serverSocket = null;
        this.connectionID = 0;
        this.stopCause = null;
        this.stopException = null;
    }

    @Override
    public void run() {
        try {
            this.init();
            this.initServerSocket();
            this.processConnections();
        } catch (Exception ex) {
            this.stop(ex);
        } finally {
            try {
                this.stopConnections();
                this.stopServerSocket();
            } catch (Exception ex) {
                Logger.getInstance().log(Level.FINER, null, ex);
            }
        }

        this.setState(ServerState.STOPED);
    }

    private void initServerSocket() throws IOException {
        this.serverSocket = new ServerSocket(this.port);
        this.serverSocket.setSoTimeout(1000);
    }

    private void stopServerSocket() {
        try {
            this.serverSocket.close();
        } catch (Exception ex) {
            Logger.getInstance().log(Level.FINER, null, ex);
        }
    }

    private void processConnections() throws Exception {
        this.setState(ServerState.RUNNING);

        while (this.getState() != ServerState.STOPPING) {
            try {
                Socket acceptedConnection = this.serverSocket.accept();

                if (acceptedConnection.isConnected()) {
                    final ConnectionHandler connectionHandler = new ConnectionHandler(acceptedConnection);
                    final ConnectionThread connectionThread = new ConnectionThread(this.connectionID++, connectionHandler);

                    connectionHandler.addConnectionStateChangedListener(new ConnectionStateChangedListener() {

                        @Override
                        public void stateChanged(ConnectionStateChangedEvent evt) {
                            if (evt.getState() == ConnectionState.RUNNING) {
                                synchronized (Server.this.connections) {
                                    Server.this.connections.add(connectionThread);
                                }
                                Server.this.fireClientConnected(new ServerConnectionEvent(Server.this, connectionThread));
                            } else if (evt.getState() == ConnectionState.STOPED) {

                                ConnectionThread connectionThreadToRemove = null;
                                synchronized (Server.this.connections) {
                                    for (ConnectionThread connection : Server.this.connections) {
                                        if (connection.getConnectionHandler() == (ConnectionHandler) evt.getSource()) {
                                            connectionThreadToRemove = connection;
                                        }
                                    }
                                    if (connectionThreadToRemove != null) {
                                        Server.this.connections.remove(connectionThreadToRemove);
                                    }
                                }
                                Server.this.fireClientDisconnected(new ServerConnectionEvent(Server.this, connectionThread));
                            }
                        }
                    });

                    connectionHandler.addCustomMessageRecievedListener(new CustomMessageRecievedListener() {

                        @Override
                        public void customMessageRecieved(final CustomMessageRecievedEvent evt) throws ClassNotFoundException, SecurityException, NoSuchMethodException {
                            Message message = evt.getMessage();

                            if (message instanceof InvocationResponsiveMessage) {
                                remoteDispatcher.invoke((InvocationResponsiveMessage) message, connectionHandler);
                            } else {
                                final Message finalMessage = message;
                                Thread thread = new Thread(new Runnable() {

                                    @Override
                                    public void run() {
                                        ClientMessage clientMessage = (ClientMessage) finalMessage;
                                        switch (clientMessage.getClientCode()) {
                                            case REQUEST_STOP:
                                                ((ConnectionHandler) evt.getSource()).stop();
                                                break;
                                        }
                                    }
                                }, message.toString());
                                Server.this.fireThreadCreated(new ThreadCreatedEvent(this, thread));
                                thread.start();
                            }
                        }
                    });

                    connectionHandler.addThreadListener(new ThreadListener() {

                        @Override
                        public void threadCreated(ThreadCreatedEvent evt) {
                            Server.this.fireThreadCreated(evt);
                        }
                    });

                    connectionHandler.addConnectionHandlerMessageTransportListener(new ConnectionHandlerMessageTransportListener() {

                        @Override
                        public void messageTransported(ConnectionHandlerMessageTransportEvent evt) {
                            Server.this.fireMessageTransported(new ConnectionHandlerMessageTransportEvent(connectionThread, evt.getMessageDirection(), evt.getMessage()));
                        }
                    });

                    this.fireThreadCreated(new ThreadCreatedEvent(this, connectionThread));
                    connectionThread.start();
                }
            } catch (SocketTimeoutException ex) {
                // Expected exception
            }
        }
    }

    public void stop() throws IOException {
        this.setState(ServerState.STOPPING);
        this.setStopCause(StopCause.REQUESTED);
    }

    public void stop(Exception ex) {
        this.setState(ServerState.STOPPING);
        this.setStopCause(StopCause.EXCEPTION);
        this.setStopException(ex);
    }

    private void stopConnections() throws IOException {
        synchronized (this.connections) {
            for (ConnectionThread connection : this.connections) {
                if (connection.isAlive()) {
                    connection.getConnectionHandler().setWaitingDisconnection(true);
                    connection.getConnectionHandler().sendMessage(new ServerMessage(ServerCode.REQUEST_STOP));
                }
            }
        }
    }

    public ServerState getState() {
        return state;
    }

    public StopCause getStopCause() {
        return stopCause;
    }

    private void setStopCause(StopCause stopCause) {
        this.stopCause = stopCause;
    }

    public Exception getStopException() {
        return stopException;
    }

    private void setStopException(Exception stopException) {
        this.stopException = stopException;
    }

    private void setState(ServerState state) {
        this.state = state;
        this.fireServerStateChanged(new ServerStateChangedEvent(this, state));
    }

    public void registerImplementation(Class<?> interfaceClass, Object implementation) {
        this.remoteDispatcher.registerImplementation(interfaceClass, implementation);
    }

    // <editor-fold defaultstate="collapsed" desc="Events">
    public void addServerStateChangedListener(ServerStateChangedListener listener) {
        this.listenerList.add(ServerStateChangedListener.class, listener);
    }

    public void removeServerStateChangedListener(ServerStateChangedListener listener) {
        this.listenerList.remove(ServerStateChangedListener.class, listener);
    }

    void fireServerStateChanged(ServerStateChangedEvent evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ServerStateChangedListener.class) {
                ((ServerStateChangedListener) listeners[i + 1]).stateChanged(evt);
            }
        }
    }

    public void addThreadListener(ThreadListener listener) {
        this.listenerList.add(ThreadListener.class, listener);
    }

    public void removeThreadListener(ThreadListener listener) {
        this.listenerList.remove(ThreadListener.class, listener);
    }

    void fireThreadCreated(ThreadCreatedEvent evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ThreadListener.class) {
                ((ThreadListener) listeners[i + 1]).threadCreated(evt);
            }
        }
    }

    public void addConnectionHandlerMessageTransportListener(ConnectionHandlerMessageTransportListener listener) {
        this.listenerList.add(ConnectionHandlerMessageTransportListener.class, listener);
    }

    public void removeConnectionHandlerMessageTransportListener(ConnectionHandlerMessageTransportListener listener) {
        this.listenerList.remove(ConnectionHandlerMessageTransportListener.class, listener);
    }

    void fireMessageTransported(ConnectionHandlerMessageTransportEvent evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ConnectionHandlerMessageTransportListener.class) {
                ((ConnectionHandlerMessageTransportListener) listeners[i + 1]).messageTransported(evt);
            }
        }
    }

    public void addConnectionHandlerMessageTransportListener(ServerConnectionListener listener) {
        this.listenerList.add(ServerConnectionListener.class, listener);
    }

    public void removeConnectionHandlerMessageTransportListener(ServerConnectionListener listener) {
        this.listenerList.remove(ServerConnectionListener.class, listener);
    }

    void fireClientConnected(ServerConnectionEvent evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ServerConnectionListener.class) {
                ((ServerConnectionListener) listeners[i + 1]).clientConnected(evt);
            }
        }
    }

    void fireClientDisconnected(ServerConnectionEvent evt) {
        Object[] listeners = this.listenerList.getListenerList();
        // Each listener occupies two elements - the first is the listener class
        // and the second is the listener instance
        for (int i = 0; i < listeners.length; i += 2) {
            if (listeners[i] == ServerConnectionListener.class) {
                ((ServerConnectionListener) listeners[i + 1]).clientDisconnected(evt);
            }
        }
    }
    // </editor-fold>
}